"""
@File    :   service.py
@Time    :   2024/03/04 09:26:13
@Author  :   Qrj
@Email   :   923207736@qq.com
@description   :   Oauth2客户端相关的curd方法类
"""

from datetime import datetime

from cashews import NOT_NONE
from sqlalchemy import and_, delete, func, insert, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from src.business.admin.system.oauth2.auth.enums import OAuth2GrantTypeEnum
from src.exceptions.custom_async_validator import generate_async_validator_exception
from src.schemas.validate import (
    OrderBySearchValidateSchema,
    PaginationSearchValidateSchema,
)
from src.utils.cashews import CashewsCondition, data_cache

from .models import OAuth2Client
from .schemas.out import (
    ListOauth2ClientCompleteOutSchema,
    Oauth2ClientCompleteOutSchema,
)
from .schemas.validate import (
    Oauth2ClientCreateValidateSchema,
    Oauth2ClientDeleteValidateSchema,
    Oauth2ClientListSearchValidateSchema,
    Oauth2ClientUpdateStatusValidateSchema,
    Oauth2ClientUpdateValidateSchema,
)


class Oauth2ClientService:
    """Oauth2客户端相关Curd方法类"""

    @data_cache(
        ttl="30m",
        key="oauth2_client#id:{tenant_id}:{oauth2_client_id}",
        tags=[
            "oauth2_client#id:{tenant_id}",
            "oauth2_client#id:{tenant_id}:{oauth2_client_id}",
        ],
        condition=NOT_NONE,
    )
    @staticmethod
    async def get_oauth2_client_use_id(
        db_session: AsyncSession,
        tenant_id: int,
        oauth2_client_id: int,
        raise_not_exists_exception: bool = True,
        raise_disabled_exception: bool = True,
        raise_expired_exception: bool = True,
    ) -> Oauth2ClientCompleteOutSchema | None:
        """通过主键获取Oauth2客户端信息实例

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            oauth2_client_id (int): oauth2客户端
            raise_not_exists_exception (bool, optional): 没找到的话是否直接报错
            raise_disable_exception (bool, optional): 禁用的话是否直接报错
            raise_expire_exception (bool, optional): 过期的话是否直接报错

        Returns:
            Oauth2ClientCompleteOutSchema | None: Oauth2客户端信息实例
        """
        search_sql = select(
            *Oauth2ClientCompleteOutSchema.convert_to_sql_model_field(OAuth2Client)
        ).where(
            OAuth2Client.id == oauth2_client_id, OAuth2Client.tenant_id == tenant_id
        )
        async with db_session.begin():
            search_res = await db_session.execute(search_sql)
        result = search_res.mappings().first()
        if result is not None:
            result = Oauth2ClientCompleteOutSchema.model_validate(result)
            if (not result.status) and raise_disabled_exception:
                # 禁用则报错
                raise generate_async_validator_exception("id", "OAuth2客户端已被禁用")
            if (
                (not result.is_init)
                and result.expiration_datetime < datetime.now()
                and raise_expired_exception
            ):
                # 非初始Oauth2客户端, 过期则报错
                raise generate_async_validator_exception("id", "OAuth2客户端已过期")
            return result
        elif result is None and raise_not_exists_exception:
            # 未找到则报错
            raise generate_async_validator_exception("id", "OAuth2客户端不存在")
        else:
            return result

    @data_cache(
        ttl="30m",
        key="oauth2_client#client_id:{tenant_id}:{oauth2_client_client_id}",
        tags=[
            "oauth2_client#client_id:{tenant_id}",
            "oauth2_client#client_id:{tenant_id}:{oauth2_client_client_id}",
        ],
        condition=NOT_NONE,
    )
    @staticmethod
    async def get_oauth2_client_use_client_id(
        db_session: AsyncSession,
        tenant_id: int,
        oauth2_client_client_id: str,
        raise_not_exists_exception: bool = True,
        raise_disabled_exception: bool = True,
        raise_expired_exception: bool = True,
    ) -> Oauth2ClientCompleteOutSchema | None:
        """获取Oauth2客户端信息实例

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int | None, optional): 租户ID
            client_id (str): oauth2客户端编号
            raise_not_exists_exception (bool, optional): 没找到的话是否直接报错
            raise_disable_exception (bool, optional): 禁用的话是否直接报错
            raise_expire_exception (bool, optional): 过期的话是否直接报错

        Returns:
            Oauth2ClientCompleteOutSchema | None: Oauth2客户端信息实例
        """
        search_sql = select(
            *Oauth2ClientCompleteOutSchema.convert_to_sql_model_field(OAuth2Client)
        ).where(
            OAuth2Client.client_id == oauth2_client_client_id,
            OAuth2Client.tenant_id == tenant_id,
        )
        async with db_session.begin():
            search_res = await db_session.execute(search_sql)
        result = search_res.mappings().first()
        if result is not None:
            result = Oauth2ClientCompleteOutSchema.model_validate(result)
            if (not result.status) and raise_disabled_exception:
                # 禁用则报错
                raise generate_async_validator_exception("id", "OAuth2客户端已被禁用")
            if (
                (not result.is_init)
                and result.expiration_datetime < datetime.now()
                and raise_expired_exception
            ):
                # 过期则报错
                raise generate_async_validator_exception("id", "OAuth2客户端已过期")
            return result
        elif result is None and raise_not_exists_exception:
            # 未找到则报错
            raise generate_async_validator_exception("id", "OAuth2客户端不存在")
        else:
            return result

    @data_cache(
        ttl="3m",
        key="complete_oauth2_client_list:{tenant_id}:{search_schema}:{pagination_schema}:{order_schema}",
        tags=["complete_oauth2_client_list:{tenant_id}"],
        condition=CashewsCondition.condition_only_true_result,
    )
    @staticmethod
    async def list_search_oauth2_client(
        db_session: AsyncSession,
        tenant_id: int,
        search_schema: Oauth2ClientListSearchValidateSchema,
        pagination_schema: PaginationSearchValidateSchema,
        order_schema: OrderBySearchValidateSchema,
        need_select_total: bool = True,
    ) -> tuple[list[Oauth2ClientCompleteOutSchema], int | None]:
        """完整信息的oauth2客户端列表查询

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id: 租户ID
            search_schema (Oauth2ClientListSearchValidateSchema): 查询参数
            pagination_schema: 分页参数
            order_schema: 排序参数
            need_select_total: 是否需要查询总数

        Returns:
            tuple[list[Oauth2ClientCompleteOutSchema], int | None]: 查询结果、查询总数
        """
        # 构建排序字段
        order_by_field_sql = order_schema.convert_to_order_by_sql(
            search_schema, OAuth2Client
        )
        # 构建查询sql
        list_search_sql = (
            select(
                *Oauth2ClientCompleteOutSchema.convert_to_sql_model_field(OAuth2Client)
            )
            .where(
                and_(*search_schema.convert_to_where_sql(OAuth2Client)),
                OAuth2Client.tenant_id == tenant_id,
            )
            .order_by(order_by_field_sql)
            .offset(pagination_schema.offset)
            .limit(pagination_schema.limit)
        )
        # 开始查询
        async with db_session.begin():
            list_search_res = await db_session.execute(list_search_sql)
        result = list_search_res.mappings().all()
        # 根据need_select_total参数来确定是否需要查询总数
        if need_select_total:
            total_sql = (
                select(func.count())
                .select_from(OAuth2Client)
                .where(
                    and_(*search_schema.convert_to_where_sql(OAuth2Client)),
                    OAuth2Client.tenant_id == tenant_id,
                )
            )
            async with db_session.begin():
                total_res = await db_session.execute(total_sql)
            total = total_res.scalar()
        else:
            total = None
        # 统一转为pydantic列表模型
        return ListOauth2ClientCompleteOutSchema.validate_python(result), total

    @staticmethod
    async def create_oauth2_client(
        db_session: AsyncSession,
        tenant_id: int,
        create_schema: Oauth2ClientCreateValidateSchema,
        creator_username: str = "",
        is_init: bool = False,
    ) -> int:
        """创建Oauth2客户端

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            create_schema (Oauth2ClientCreateValidateSchema): 创建所需的信息
            creator_username (str, optional): 创建者用户名
            is_init (bool, optional): 是否初始创建的Oauth2客户端

        Returns:
            int: 创建的sql的主键ID
        """
        # 构建用户的创建信息字典(排除password参数)
        create_info_dict = create_schema.model_dump()
        # 创建
        create_sql = insert(OAuth2Client).values(
            tenant_id=tenant_id,
            creator_username=creator_username,
            is_init=is_init,
            **create_info_dict,
        )
        async with db_session.begin():
            create_res = await db_session.execute(create_sql)
            # 清空相关缓存标签
            await data_cache.delete_tags(
                f"complete_oauth2_client_list:{tenant_id}",
            )
        return create_res.inserted_primary_key_rows[0][0]

    @staticmethod
    @data_cache.transaction()
    async def update_oauth2_client(
        db_session: AsyncSession,
        tenant_id: int,
        update_schema: Oauth2ClientUpdateValidateSchema,
        updater_username: str = "",
    ):
        """修改Oauth2客户端信息

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            update_schema (Oauth2ClientUpdateValidateSchema): 修改操作所需的数据
            updater_username (str, optional):修改者用户名
        """
        # 检查需修改的sql实例是否存在(禁用不报错)
        updated_oaiuth2_client_obj = await Oauth2ClientService.get_oauth2_client_use_id(
            db_session, tenant_id, update_schema.id, raise_disabled_exception=False
        )
        # 构建修改所需的数据(排除未设置的值和id)
        # 只允许修改初始Oauth2客户端的"访问令牌过期秒数"、"刷新令牌过期秒数"、"授权码过期秒数"
        update_schema_data = (
            update_schema.model_dump(exclude=("id",), exclude_unset=True)
            if not updated_oaiuth2_client_obj.is_init
            else update_schema.model_dump(
                include=(
                    "access_token_expiration_seconds",
                    "refresh_token_expiration_seconds",
                    "authorization_code_expiration_seconds",
                ),
                exclude_unset=True,
            )
        )
        if update_schema_data:
            # 修改sql
            update_sql = (
                update(OAuth2Client)
                .values(**update_schema_data, updater_username=updater_username)
                .where(
                    OAuth2Client.id == update_schema.id,
                    OAuth2Client.tenant_id == tenant_id,
                )
            )
            # 修改操作
            async with db_session.begin():
                # 修改sql数据
                await db_session.execute(update_sql)
                # 清空相关缓存标签
                await data_cache.delete_tags(
                    f"oauth2_client#id:{tenant_id}:{update_schema.id}",
                    f"oauth2_client#client_id:{tenant_id}:{updated_oaiuth2_client_obj.client_id}",
                    f"complete_oauth2_client_list:{tenant_id}",
                )

    @staticmethod
    @data_cache.transaction()
    async def update_oauth2_client_status(
        db_session: AsyncSession,
        tenant_id: int,
        update_schema: Oauth2ClientUpdateStatusValidateSchema,
        updater_username: str = "",
    ):
        """单独修改Oauth2客户端状态

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            update_schema (Oauth2ClientUpdateStatusValidateSchema): 修改操作所需的数据
            updater_username (str, optional):修改者用户名
        """
        # 检查需修改的sql实例是否存在(禁用不报错)
        updated_oaiuth2_client_obj = await Oauth2ClientService.get_oauth2_client_use_id(
            db_session, tenant_id, update_schema.id, raise_disabled_exception=False
        )
        # 禁止修改初始Oauth2客户端
        if updated_oaiuth2_client_obj.is_init:
            raise generate_async_validator_exception(
                "id", "禁止修改初始的Oauth2客户端的状态"
            )
        # 修改sql
        update_sql = (
            update(OAuth2Client)
            .values(status=update_schema.status, updater_username=updater_username)
            .where(
                OAuth2Client.id == update_schema.id, OAuth2Client.tenant_id == tenant_id
            )
        )
        # 修改操作
        async with db_session.begin():
            # 修改sql数据
            await db_session.execute(update_sql)
            # 清空相关缓存标签
            await data_cache.delete_tags(
                f"oauth2_client#id:{tenant_id}:{update_schema.id}",
                f"oauth2_client#client_id:{tenant_id}:{updated_oaiuth2_client_obj.client_id}",
                f"complete_oauth2_client_list:{tenant_id}",
            )

    @staticmethod
    @data_cache.transaction()
    async def delete_oauth2_client(
        db_session: AsyncSession,
        tenant_id: int,
        delete_schema: Oauth2ClientDeleteValidateSchema,
    ):
        """删除oauth2客户端

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id: 租户ID
            delete_schema (Oauth2ClientDeleteValidateSchema): 删除操作所属的数据
        """
        # 查找需删除的oauth2客户端
        # 构建查询sql
        select_sql = select(
            OAuth2Client.id, OAuth2Client.is_init, OAuth2Client.client_id
        ).where(
            OAuth2Client.id.in_(delete_schema.ids), OAuth2Client.tenant_id == tenant_id
        )
        # 进行sql查询
        async with db_session.begin():
            select_res = await db_session.execute(select_sql)
        select_result = select_res.mappings().all()
        # 检查是否删除了初始Oauth2客户端
        if any([i["is_init"] for i in select_result]):
            raise generate_async_validator_exception(
                "ids", "不允许删除初始Oauth2客户端"
            )
        # 进行删除操作
        if select_result:
            # 获取需要删除的id列表
            delete_id_list = [i["id"] for i in select_result]
            delete_client_id_list = [i["client_id"] for i in select_result]
            # 删除Oauth2客户端本身的sql
            delete_sql = delete(OAuth2Client).where(OAuth2Client.id.in_(delete_id_list))
            # 删除操作
            async with db_session.begin():
                # 删除sql中的数据
                await db_session.execute(delete_sql)
                delete_cache_tags = [
                    *[f"oauth2_client#id:{tenant_id}:{i}" for i in delete_id_list],
                    *[
                        f"oauth2_client#client_id:{tenant_id}:{i}"
                        for i in delete_client_id_list
                    ],
                    f"complete_oauth2_client_list:{tenant_id}",
                ]
                # 清空相关缓存标签
                await data_cache.delete_tags(*delete_cache_tags)

    @staticmethod
    def validate_oauth2_client_authorized_grant_type(
        oauth2_client_authorized_grant_types: list[str],
        grant_type: OAuth2GrantTypeEnum,
        field_name: str = "grant_types",
    ):
        """验证是否允许客户端使用指定的授权模式

        Args:
            oauth2_client_authorized_grant_types: OAuth2客户端的授权类型列表
            grant_type (OAuth2GrantTypeEnum): 授权模式
            field_name: 报错使用的字段名称
        """
        if grant_type.value not in oauth2_client_authorized_grant_types:
            raise generate_async_validator_exception(
                field_name, "客户端不允许使用该OAuth2授权模式"
            )

    @staticmethod
    def validate_oauth2_client_redirect_uri(
        oauth2_client_redirect_uris: list[str],
        redirect_uri: str,
        field_name: str = "redirect_uri",
    ):
        """验证客户端的重定向uri是否在允许的可重定向的URI地址列表内

        Args:
            oauth2_client_redirect_uris: OAuth2客户端的可重定向uri列表
            redirect_uri (str): 重定向uri
            field_name: 报错使用的字段名称
        """
        if redirect_uri not in oauth2_client_redirect_uris:
            raise generate_async_validator_exception(
                field_name, "OAuth2客户端不允许使用该重定向uri"
            )

    @staticmethod
    def validate_oauth2_client_secret(
        oauth2_client_secret: str,
        client_secret: str,
        field_name: str = "client_secret",
    ):
        """验证OAuth2客户端的密钥是否正确

        Args:
            oauth2_client_secret: oauth2客户端的密钥
            client_secret (str): 需验证的客户端密钥
            field_name: 报错使用的字段名称
        """
        if client_secret != oauth2_client_secret:
            raise generate_async_validator_exception(field_name, "OAuth2客户端密钥错误")

    @staticmethod
    def generate_scopes(
        ouath2_client_scopes: list[str],
        ouath2_client_auto_approve_scopes: list[str],
        required_scopes: list[str],
    ) -> list[str]:
        """生成token的授权范围(自动赋予的oauth2授权范围 + 用户选择的在oauth2授权范围的权限)

        Args:
            ouath2_client_scopes (list[str]): oauth2客户端的授权范围
            ouath2_client_auto_approve_scopes (list[str]): oauth2客户端自动携带的授权范围
            required_scopes (list[str]): 用户所需的权限

        Returns:
            list[str]: 最终赋予的权限
        """
        return list(
            {
                *ouath2_client_auto_approve_scopes,
                *[
                    required_scope
                    for required_scope in required_scopes
                    if required_scope in ouath2_client_scopes
                ],
            }
        )
